#include "wm_i2c.h"

static void delay_us(void)
{
    uint32_t i;
    
    for (i = 0; i < (45 * 5); i++)
    {
        __NOP();
    }
}

static void I2C_Start(I2C_HandleTypeDef *hi2c)
{    
    I2C_SCL_H(hi2c);
    I2C_SDA_H(hi2c);
    delay_us();
    
    I2C_SDA_L(hi2c);
    delay_us();
    
    I2C_SCL_L(hi2c);
    delay_us();
}

static void I2C_Stop(I2C_HandleTypeDef *hi2c)
{
    I2C_SDA_L(hi2c);
    I2C_SCL_H(hi2c);
    delay_us();
    
    I2C_SDA_H(hi2c);
    delay_us();
}

static void I2C_Ack(I2C_HandleTypeDef *hi2c)
{    
    I2C_SDA_OUT(hi2c);
    I2C_SDA_L(hi2c);
    I2C_SCL_H(hi2c);
    delay_us();
    
    I2C_SCL_L(hi2c);
    delay_us();
    I2C_SDA_IN(hi2c);
}

static void I2C_Nack(I2C_HandleTypeDef *hi2c)
{
    I2C_SDA_OUT(hi2c);
    I2C_SDA_H(hi2c);
    I2C_SCL_H(hi2c);
    delay_us();
    
    I2C_SCL_L(hi2c);
    delay_us();
    I2C_SDA_IN(hi2c);
}

static uint8_t I2C_Wait_Ack(I2C_HandleTypeDef *hi2c)
{
    uint8_t ack;

    I2C_SDA_H(hi2c);
    delay_us();
    I2C_SDA_IN(hi2c);
    I2C_SCL_H(hi2c);
    delay_us();
    ack = I2C_SDA_GET(hi2c);
    I2C_SCL_L(hi2c);
    delay_us();
    I2C_SDA_OUT(hi2c);

    return ack;
}

static void I2C_Write_Byte(I2C_HandleTypeDef *hi2c, uint8_t data)
{
    int i;
    
    for (i = 0; i < 8; i ++)
    {
        if (data & 0x80)
        {
            I2C_SDA_H(hi2c);
        }
        else
        {
            I2C_SDA_L(hi2c);
        }
        I2C_SCL_H(hi2c);
        delay_us();
        I2C_SCL_L(hi2c);
        delay_us();
        data <<= 1;
    }
}

static uint8_t I2C_Read_Byte(I2C_HandleTypeDef *hi2c)
{
    uint8_t i, temp = 0;
    
    for (i = 0; i < 8; i++)
    {
        I2C_SCL_H(hi2c);
        delay_us();
        temp <<= 1;
        if (I2C_SDA_GET(hi2c))
        {
            temp |= 0x01;
        }
        I2C_SCL_L(hi2c);
        delay_us();
    }

    return temp;
}

HAL_StatusTypeDef HAL_I2C_Init(I2C_HandleTypeDef *hi2c)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    GPIO_InitStruct.Pin = hi2c->SCL_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(hi2c->SCL_Port, &GPIO_InitStruct);
    
    GPIO_InitStruct.Pin = hi2c->SDA_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(hi2c->SDA_Port, &GPIO_InitStruct);
    
    HAL_GPIO_WritePin(hi2c->SCL_Port, hi2c->SCL_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(hi2c->SDA_Port, hi2c->SDA_Pin, GPIO_PIN_SET);
    
    return HAL_OK;
}

HAL_StatusTypeDef HAL_I2C_DeInit(I2C_HandleTypeDef *hi2c)
{
    HAL_GPIO_DeInit(hi2c->SCL_Port, hi2c->SCL_Pin);
    HAL_GPIO_DeInit(hi2c->SDA_Port, hi2c->SDA_Pin);
    
    return HAL_OK;
}

HAL_StatusTypeDef HAL_I2C_Write(I2C_HandleTypeDef *hi2c, uint8_t DevAddress, uint8_t MemAddress, uint8_t *pData, uint16_t Size)
{
    uint32_t i, ret = HAL_ERROR;
    
    I2C_Start(hi2c);
    I2C_Write_Byte(hi2c, (DevAddress & 0xFE));
    if (I2C_Wait_Ack(hi2c))
    {
        goto OUT;
    }
    I2C_Write_Byte(hi2c, MemAddress);
    if (I2C_Wait_Ack(hi2c))
    {
        goto OUT;
    }
    for (i = 0; i < Size; i++)
    {
        I2C_Write_Byte(hi2c, pData[i]);
        if (I2C_Wait_Ack(hi2c))
        {
            goto OUT;
        }
    }
    ret = HAL_OK;
OUT:
    I2C_Stop(hi2c);
    return ret;
}

HAL_StatusTypeDef HAL_I2C_Read(I2C_HandleTypeDef *hi2c, uint8_t DevAddress, uint8_t MemAddress, uint8_t *pData, uint16_t Size)
{
    uint32_t i, ret = HAL_ERROR;
    
    I2C_Start(hi2c);
    I2C_Write_Byte(hi2c, (DevAddress & 0xFE));
    if (I2C_Wait_Ack(hi2c))
    {
        goto OUT;
    }
    I2C_Write_Byte(hi2c, MemAddress);
    if (I2C_Wait_Ack(hi2c))
    {
        goto OUT;
    }
    I2C_Start(hi2c);
    I2C_Write_Byte(hi2c, (DevAddress | 0x01));
    if (I2C_Wait_Ack(hi2c))
    {
        goto OUT;
    }
    I2C_SDA_IN(hi2c);
    for (i = 0; i < Size; i++)
    {
        pData[i] = I2C_Read_Byte(hi2c);
        if (i == (Size - 1))
        {
            I2C_Nack(hi2c);
        }
        else
        {
            I2C_Ack(hi2c);
        }
    }
    ret = HAL_OK;
OUT:
    I2C_SDA_OUT(hi2c);
    I2C_Stop(hi2c);
    return ret;
}
